# -*- coding: utf-8 -*-

'''订单相关，抽象方法主要使用函数式编程
    这里应该设计成面向对象的…时间原因，先这么写吧。。
'''
import json
import logging

from decimal import Decimal
from future.utils import raise_with_traceback

from api.coupon.handler import check_coupon_valid
from async import async_job
from common import orm
from common.account.db.account import decrease_balance_in_transaction
from common.account.model.account import Account
from common.cache import redis_cache
from common.cache.redis_cache import remove_ongoing_track, add_unread_win
from common.coupon.db import get_coupon
from common.coupon.model import AccountCoupon, COUPON_STATUS, COUPON_TYPE
from common.lottery import LOTTERY_TYPE
from common.lottery.cyclical.bj_pk10.model.order import BET_TYPE as BJ_PK10_BET_TYPE
from common.lottery.cyclical.ff_pk10.model.order import BET_TYPE as FF_PK10_BET_TYPE
from common.lottery.cyclical.ff_ks.model.order import BET_TYPE as FF_KS_BET_TYPE
from common.lottery.cyclical.ssc.model import BET_TYPE as SSC_BET_TYPE
from common.lottery.cyclical.gx_ks.model.order import BET_TYPE as GX_KS_BET_TYPE
from common.lottery.cyclical.js_ks.model.order import BET_TYPE as JS_KS_BET_TYPE
from common.lottery.cyclical import ACTIVITY_STATUS, ORDER_STATUS, ORDER_TYPE, TRACK_STATUS
from common.lottery.cyclical.model import (
    ACTIVITY_MODEL, ORDER_MODEL)
from common.lottery.model import OrderIndex, TrackIndex
from common.preset.db.reward import check_reward
from common.preset.model.preset import RewardLimit, Reward
from common.transaction.db import create_sharding_transaction
from common.transaction.model import TRANSACTION_TYPE, TRANSACTION_STATUS
from common.utils import exceptions as err
from common.utils import tracker
from common.utils import tz, EnhencedEncoder
from common.utils.exceptions import DataError
from common.utils.types import Enum

_LOGGER = logging.getLogger(__name__)
MAX_PRIZE = 100000  # 超过 10w 要审核

REFUND_TYPE = Enum({
    'STOP_TRACK': (1L, u'停追返款'),
    'CANCEL_ORDER': (2L, u'撤单返款')
})


def get_history(activity_type, user_id, limit, offset, track_id):
    table = ORDER_MODEL[activity_type]
    query = table.query.filter(table.user_id == user_id)
    if track_id:
        query = query.filter(table.track_id == long(track_id))
        query = query.order_by(table.id)
    else:
        query = query.order_by(table.id.desc())
    if limit:
        query = query.limit(limit)
    if offset:
        query = query.offset(offset)

    return query.all()


def get_order(activity_type, user_id, order_id):
    table = ORDER_MODEL[activity_type]
    order = table.query.filter(
        table.user_id == user_id).filter(
        table.id == order_id).one()
    return order


def cancel_orders(activity_type, user_id, order_ids):
    success_list = []
    fail_list = []
    success_terms = []
    total_price = 0
    order_table = ORDER_MODEL[activity_type]
    activity_table = ACTIVITY_MODEL[activity_type]
    track_ids = set()
    for order_id in order_ids:
        order = order_table.query.with_for_update().filter(
            order_table.id == order_id).filter(
            order_table.user_id == user_id).filter(
            order_table.status == ORDER_STATUS.READY).first()
        if not order:
            continue
        activity = activity_table.query.filter(
            activity_table.term == order.term).first()
        # 通过时间和状态判断是否已经开奖，保守策略
        # activity不存在则说明
        if activity and (activity.status != ACTIVITY_STATUS.STARTED or
                         tz.now_ts() > activity.end_ts):
            fail_list.append(order_id)
            continue
        if order.track_id and not activity:
            # 追号订单且活动未开始可以撤销
            track_ids.add(order.track_id)
        else:
            fail_list.append(order_id)
            continue
        account = Account.query.with_for_update().filter(
            Account.id == user_id).one()
        index = OrderIndex.query.filter(
            OrderIndex.order_id == order.id).filter(
            OrderIndex.activity_type == activity_type).first()
        # 订单状态
        if index is not None:
            index.status = ORDER_STATUS.CANCEL
            index.save(auto_commit=False)
        order.status = ORDER_STATUS.CANCEL
        if order.track_id:
            order.track_status = TRACK_STATUS.CANCEL
        order.save(auto_commit=False)
        # 用户余额
        account.balance += order.price
        account.save(auto_commit=False)
        # 流水记录
        if order.track_id:
            create_sharding_transaction(account.id, {'type': TRANSACTION_TYPE.REFUND,
                                                     'title': u'追号返款',
                                                     'activity_type': activity_type,
                                                     'order_id': order.id,
                                                     'price': order.price,
                                                     'balance': account.balance,
                                                     'status': TRANSACTION_STATUS.DONE,
                                                     })
        else:
            create_sharding_transaction(account.id, {'type': TRANSACTION_TYPE.REFUND,
                                                     'title': u'撤单返款',
                                                     'activity_type': activity_type,
                                                     'order_id': order.id,
                                                     'price': order.price,
                                                     'balance': account.balance,
                                                     'status': TRANSACTION_STATUS.DONE,
                                                     })

        orm.session.commit()
        success_list.append(order_id)
        success_terms.append(order.term)
        total_price += order.price
        refund_type = REFUND_TYPE.CANCEL_ORDER
        tracker.track_refund(user_id, activity_type, order.price, refund_type, order.bet_type)

    async_job.stats_refund.delay(user_id, activity_type, order.bet_type, success_terms, total_price,
                                 REFUND_TYPE.CANCEL_ORDER)
    for track_id in track_ids:
        try_stop_track_index(order_table, track_id)
    return success_list, fail_list


def manual_bonus_orders(activity_type, order_ids):
    success_list = []
    fail_list = []
    total_price = 0
    order_table = ORDER_MODEL[activity_type]
    track_ids = set()
    for order_id in order_ids:
        order = order_table.query.with_for_update().filter(
            order_table.id == order_id).filter(
            order_table.status == ORDER_STATUS.WINNED).filter(
            order_table.bonus == 0).first()
        if not order:
            continue
        account = Account.query.with_for_update().filter(
            Account.id == order.user_id).one()
        hits = check_reward(activity_type, order.term, order.bet_type)
        bonus, withdraw_bonus = WinOrderApplyer.calc_bonus(order.win_price, hits)
        if bonus > 0:
            order.bonus = bonus
            order.save(auto_commit=False)
            # 用户余额
            account.balance += bonus
            account.withdraw += withdraw_bonus
            account.save(auto_commit=False)
            create_sharding_transaction(order.user_id, {'type': TRANSACTION_TYPE.AWARD,
                                                        'title': u'加奖-{}'.format(order.term),
                                                        'activity_type': activity_type,
                                                        'price': order.bonus,
                                                        'order_id': order.id,
                                                        'balance': account.balance,
                                                        'status': TRANSACTION_STATUS.DONE,
                                                        })

        orm.session.commit()
        tracker.track_win(order.user_id, activity_type, order.bet_type, 0, order.bonus)
        success_list.append(order_id)
        total_price += order.bonus
    return success_list, fail_list, total_price


def manual_cancel_orders(activity_type, order_ids):
    success_list = []
    fail_list = []
    success_terms = []
    total_price = 0
    order_table = ORDER_MODEL[activity_type]
    activity_table = ACTIVITY_MODEL[activity_type]
    track_ids = set()
    for order_id in order_ids:
        order = order_table.query.with_for_update().filter(
            order_table.id == order_id).filter(
            order_table.status == ORDER_STATUS.READY).first()
        if not order:
            continue
        activity = activity_table.query.filter(
            activity_table.term == order.term).first()
        # 通过时间和状态判断是否已经开奖，保守策略
        account = Account.query.with_for_update().filter(
            Account.id == order.user_id).one()
        index = OrderIndex.query.filter(
            OrderIndex.order_id == order.id).filter(
            OrderIndex.activity_type == activity_type).first()
        # 订单状态
        if index is not None:
            index.status = ORDER_STATUS.CANCEL
            index.save(auto_commit=False)
        order.status = ORDER_STATUS.CANCEL
        order.save(auto_commit=False)
        # 用户余额
        account.balance += order.price
        account.save(auto_commit=False)
        # 流水记录
        create_sharding_transaction(account.id, {'type': TRANSACTION_TYPE.REFUND,
                                                 'title': u'客服撤单返款',
                                                 'activity_type': activity_type,
                                                 'order_id': order.id,
                                                 'price': order.price,
                                                 'balance': account.balance,
                                                 'status': TRANSACTION_STATUS.DONE,
                                                 })

        orm.session.commit()
        success_list.append(order_id)
        success_terms.append(order.term)
        total_price += order.price
        refund_type = REFUND_TYPE.CANCEL_ORDER
        tracker.track_refund(order.user_id, activity_type, order.price, refund_type, order.bet_type)
    return success_list, fail_list


def try_stop_track_index(order_table, track_id):
    inst = order_table.query.filter(
        order_table.track_status == TRACK_STATUS.READY).filter(
        order_table.track_id == track_id).first()
    if inst is None:  # 没有进行中的追号了
        track_index = TrackIndex.query.with_for_update().filter(
            TrackIndex.track_id == track_id).one()
        track_index.status = TRACK_STATUS.TRACKED
        track_index.save()
        remove_ongoing_track(track_index.user_id, track_index.track_id)


def move_to_tracked(activity_type, term):
    table = ORDER_MODEL[activity_type]
    orders = table.query.filter(
        table.term == term).filter(
        table.track_status == TRACK_STATUS.READY).all()
    track_ids = set()
    for order in orders:
        order.track_status = TRACK_STATUS.TRACKED
        order.save(auto_commit=False)
        track_ids.add(order.track_id)

        index = OrderIndex()
        index.user_id = order.user_id
        index.order_id = order.id
        index.activity_type = activity_type
        index.save(auto_commit=False)

        # update track info to cache
        redis_cache.increase_track_info(order.track_id, 'bet_count', 1)
        redis_cache.increase_track_info(order.track_id, 'bet_amount', order.price)
    orm.session.commit()

    for track_id in track_ids:
        try:
            try_stop_track_index(table, track_id)
        except Exception as e:
            _LOGGER.exception('try_stop_track_index error: %s' % e)


class WinOrderApplyer(object):
    def __init__(self, term, number, logic_module, activity_type):
        self.term = str(term)
        self.number = number
        self.logic_module = logic_module
        self.funcs = logic_module.generate_win_func(number)
        self.activity_type = activity_type
        self.order_table = ORDER_MODEL[activity_type]

    def __refund(self, ids):
        if not ids:
            return
        total_price = 0
        refund_terms = []
        refund_ids = []
        refund_order_ids = []
        for i in ids:
            order = self.order_table.query.with_for_update().filter(
                self.order_table.id == i).filter(
                self.order_table.status == ORDER_STATUS.READY).filter(
                self.order_table.type == ORDER_TYPE.PRE_STOP_WIN).first()
            if not order:
                continue
            index = OrderIndex.query.filter(
                OrderIndex.order_id == order.id).filter(
                OrderIndex.activity_type == self.activity_type).first()
            refund_ids.append(i)
            if index:
                index.status = ORDER_STATUS.CANCEL
                index.save(auto_commit=False)
            else:
                _LOGGER.warn('#debug# index not found: %s' % i)

            if order.track_status == TRACK_STATUS.TRACKED:
                # if track_status is tracked, then move_to_track tracked unwanted order, and increased redis
                # adjust this situation
                redis_cache.increase_track_info(order.track_id, 'bet_count', -1)
                redis_cache.increase_track_info(order.track_id, 'bet_amount', -order.price)

            order.status = ORDER_STATUS.CANCEL
            order.track_status = TRACK_STATUS.STOPPED
            total_price += order.price
            refund_terms.append(order.term)
            order.save(auto_commit=False)
            refund_order_ids.append(order.id)

        account = Account.query.with_for_update().filter(
            Account.id == order.user_id).one()
        account.balance += total_price
        account.save(auto_commit=False)

        create_sharding_transaction(order.user_id, {'type': TRANSACTION_TYPE.REFUND,
                                                    'activity_type': self.activity_type,
                                                    'title': u'追号返款',
                                                    'price': total_price,
                                                    'balance': account.balance,
                                                    'extend': json.dumps({'order_ids': refund_ids}),
                                                    'status': TRANSACTION_STATUS.DONE,
                                                    })
        refund_type = REFUND_TYPE.STOP_TRACK
        tracker.track_refund(order.user_id, self.activity_type, total_price, refund_type, order.bet_type)
        async_job.stats_refund.delay(order.user_id, self.activity_type, order.bet_type, refund_terms, total_price,
                                     refund_type)

    def __insert_transaction(self, order, balance):
        balance += Decimal(order.win_price)
        create_sharding_transaction(order.user_id, {'type': TRANSACTION_TYPE.AWARD,
                                                    'activity_type': self.activity_type,
                                                    'title': u'{}派奖'.format(
                                                        LOTTERY_TYPE.get_label(self.activity_type)),
                                                    'price': order.win_price,
                                                    'balance': balance,
                                                    'order_id': order.id,
                                                    'status': TRANSACTION_STATUS.DONE,
                                                    })

        if order.bonus > 0:
            balance += Decimal(order.bonus)
            create_sharding_transaction(order.user_id, {'type': TRANSACTION_TYPE.AWARD,
                                                        'activity_type': self.activity_type,
                                                        'title': u'{}加奖'.format(
                                                            LOTTERY_TYPE.get_label(self.activity_type)),
                                                        'price': order.bonus,
                                                        'balance': balance,
                                                        'order_id': order.id,
                                                        'status': TRANSACTION_STATUS.DONE,
                                                        })

    @staticmethod
    def __update_account(order, withdraw_bonus):
        account = Account.query.with_for_update().filter(
            Account.id == order.user_id).one()
        balance = account.balance
        account.balance += Decimal(order.win_price) + Decimal(order.bonus)
        account.withdraw += Decimal(order.win_price) + Decimal(withdraw_bonus)
        account.save(auto_commit=False)
        return balance

    @classmethod
    def calc_bonus(cls, win_price, hits):
        total_bonus = withdraw_bonus = 0
        for hit in hits:
            bonus = (win_price * hit['rate']) / 100 + hit['amount']
            expire = False
            if hit['limit']:
                # 奖金限额
                if redis_cache.decr_reward_limit(hit['limit'], bonus) <= 0:
                    expire = True
                    # 限额归0
                    limit = RewardLimit.query.filter(
                        RewardLimit.id == hit['limit']).one()
                    limit.left = 0
                    limit.save(auto_commit=False)
                    # 关闭加奖
                    reward = Reward.query.filter(Reward.id == hit['id']).one()
                    reward.enable = False
                    reward.save(auto_commit=False)
            if not expire:
                total_bonus += bonus
                if hit['withdraw']:
                    withdraw_bonus += bonus

        return total_bonus, withdraw_bonus

    def __apply(self, order_id, funcs):
        order = self.order_table.query.with_for_update().filter(
            self.order_table.id == order_id).filter(
            self.order_table.status == ORDER_STATUS.READY).first()
        if not order:
            print 'order is none'
            return None, None
        bet_price = order.price
        win_price = self.logic_module.calc_win_price(funcs, order.number, order.bet_type, order.times, order.unit)
        hits = check_reward(self.activity_type, self.term, order.bet_type)
        bonus, withdraw_bonus = WinOrderApplyer.calc_bonus(win_price, hits)
        index = OrderIndex.query.filter(
            OrderIndex.order_id == order.id).filter(
            OrderIndex.activity_type == self.activity_type).first()
        order.win_price = win_price
        order.bonus = bonus
        if win_price > 0:
            order.status = ORDER_STATUS.WINNED
            order.track_status = TRACK_STATUS.WINNED
        else:
            order.status = ORDER_STATUS.LOSE
        if win_price + bonus >= MAX_PRIZE:
            order.status = ORDER_STATUS.UNCHECK

        extend = json.loads(order.extend or '{}') or {}
        extend['withdraw_bonus'] = withdraw_bonus
        order.extend = json.dumps(extend, ensure_ascii=False, cls=EnhencedEncoder)
        order.save(auto_commit=False)
        if index is not None:
            index.status = order.status
            index.save(auto_commit=False)
        return order, withdraw_bonus

    def apply(self):
        # TODO: 可能需要优化成多进程的
        order_ids = orm.session.query(self.order_table.id).filter(
            self.order_table.term == self.term).filter(
            self.order_table.status == ORDER_STATUS.READY).all()
        user_ids = set()
        for order_id, in order_ids:
            order, withdraw_bonus = self.__apply(order_id, self.funcs)
            if not order or order.status in (ORDER_STATUS.LOSE, ORDER_STATUS.UNCHECK):
                if order and order.status == ORDER_STATUS.UNCHECK:
                    tracker.track_win(order.user_id, self.activity_type, order.bet_type, order.win_price, order.bonus)
                    if order.type == ORDER_TYPE.PRE_STOP_WIN:
                        ids = orm.session.query(self.order_table.id).filter(
                            self.order_table.term > order.term).filter(
                            self.order_table.status == ORDER_STATUS.READY).filter(
                            self.order_table.user_id == order.user_id).filter(
                            self.order_table.track_id == order.track_id).filter(
                            self.order_table.type == ORDER_TYPE.PRE_STOP_WIN).all()
                        self.__refund([i[0] for i in ids])
                        track_index = TrackIndex.query.with_for_update(). \
                            filter(TrackIndex.track_id == order.track_id).first()
                        if track_index:
                            track_index.status = TRACK_STATUS.TRACKED
                            track_index.save(auto_commit=False)
                            remove_ongoing_track(track_index.user_id, track_index.track_id)
                        else:
                            _LOGGER.warn('track index {} is lost'.format(order.track_id))
                else:
                    if order and order.track_id:
                        async_job.generate_track_detail.delay(order.track_id)
                orm.session.commit()
            else:
                user_ids.add(order.user_id)
                balance = WinOrderApplyer.__update_account(order, withdraw_bonus)
                self.__insert_transaction(order, balance)
                if order.type == ORDER_TYPE.PRE_STOP_WIN:
                    ids = orm.session.query(self.order_table.id).filter(
                        self.order_table.term > order.term).filter(
                        self.order_table.status == ORDER_STATUS.READY).filter(
                        self.order_table.user_id == order.user_id).filter(
                        self.order_table.track_id == order.track_id).filter(
                        self.order_table.type == ORDER_TYPE.PRE_STOP_WIN).all()
                    self.__refund([i[0] for i in ids])
                    track_index = TrackIndex.query.with_for_update().filter(
                        TrackIndex.track_id == order.track_id).first()
                    if track_index:
                        track_index.status = TRACK_STATUS.TRACKED
                        track_index.save(auto_commit=False)
                        remove_ongoing_track(track_index.user_id, track_index.track_id)
                    else:
                        _LOGGER.warn('track index {} is lost'.format(order.track_id))
                orm.session.commit()
                tracker.track_win(order.user_id, self.activity_type, order.bet_type, order.win_price, order.bonus)
                add_unread_win(order.user_id, self.activity_type, order.id)
                if order.status in (ORDER_STATUS.WINNED, ORDER_STATUS.UNCHECK) and (
                        order.win_price + order.bonus) > 1000:
                    async_job.stats_order_win.delay(order.id, self.activity_type)
                if order.track_id:
                    # update track info to cache
                    redis_cache.increase_track_info(order.track_id, 'win_price', order.win_price)
                    redis_cache.increase_track_info(order.track_id, 'bonus', order.bonus)
                    async_job.generate_track_detail.delay(order.track_id)
            if order:
                redis_cache.add_total_bet(self.activity_type, float(order.price))
                redis_cache.add_system_profit(self.activity_type,
                                              float(order.price) - float(order.win_price + order.bonus))
                tracker.track_gain(self.activity_type, float(order.price),
                                   float(order.win_price), float(order.bonus))
        return user_ids


class OrderPayer(object):
    _MAX_TRY = 1

    def __init__(self, activity_type):
        self.activity_type = activity_type
        self.order_table = ORDER_MODEL[activity_type]
        self.activity_table = ACTIVITY_MODEL[activity_type]

    def __create_order(self, info):
        order = self.order_table()
        order.status = ORDER_STATUS.READY
        if 'track_id' in info:
            order.track_id = info['track_id']
        order.extend = json.dumps(info.get('extend', {}))
        for k in ('user_id', 'term', 'type', 'bet_type', 'number',
                  'price', 'times', 'unit', 'track_status'):
            setattr(order, k, info[k])
        order.save(auto_commit=False)
        return order

    def __create_pay_bill(self, account, order):
        create_sharding_transaction(account.id, {'type': TRANSACTION_TYPE.BALANCE_BUY,
                                                 'activity_type': self.activity_type,
                                                 'title': u'{}购彩'.format(
                                                     LOTTERY_TYPE.get_label(self.activity_type)),
                                                 'price': -order.price,
                                                 'balance': account.balance,
                                                 'order_id': order.id,
                                                 'status': TRANSACTION_STATUS.DONE,
                                                 })

    def __create_index(self, order):
        if order.track_id is None or \
                order.track_status == TRACK_STATUS.TRACKED:  # 当前期追号订单
            inst = OrderIndex()
            inst.order_id = order.id
            inst.user_id = order.user_id
            inst.activity_type = self.activity_type
            inst.detail = json.dumps(order.as_dict(), ensure_ascii=False)
            inst.save(auto_commit=False)

    def __pay(self, info):
        account = decrease_balance_in_transaction(info['user_id'], info['price'])
        order = self.__create_order(info)
        orm.session.flush()
        self.__create_index(order)
        self.__create_pay_bill(account, order)
        orm.session.commit()
        tracker.track_pay(account.id, self.activity_type, order.bet_type, order.price, account.is_virtual)
        _LOGGER.info('%s pay order [%s] success', account.id, order.id)
        return order

    def pay(self, info):
        try_count = 1
        while try_count <= self._MAX_TRY:
            try:
                activity = self.activity_table.query.filter(
                    self.activity_table.term == info['term']).first()
                if activity and (activity.status != ACTIVITY_STATUS.STARTED or tz.now_ts() > activity.end_ts):
                    raise_with_traceback(err.TermExpired())
                if 'track_id' in info:
                    if activity:
                        info['track_status'] = TRACK_STATUS.TRACKED
                    else:
                        info['track_status'] = TRACK_STATUS.READY
                else:
                    info['track_status'] = TRACK_STATUS.NOP
                return self.__pay(info)
            except (err.TermExpired, err.BalanceInsufficient, err.LockError) as e:
                raise_with_traceback(e)
            except Exception as e:
                try_count += 1
                orm.session.rollback()
                _LOGGER.exception(
                    'account pay transaction operation error.(%s)' % e)
            finally:
                orm.session.close()

        if try_count > self._MAX_TRY:
            raise err.DbError('some error happened when pay order: %s' % info)


class BatchOrderPayer(object):
    """
    使用红包就调用此方法
    """

    def __init__(self, activity_type, user_id, coupon_id, buy_list, bet_type, is_virtual):
        self.activity_type = activity_type
        self.user_id = user_id
        self.coupon_id = coupon_id
        self.order_table = ORDER_MODEL[activity_type]
        self.activity_table = ACTIVITY_MODEL[activity_type]
        self.buy_list = buy_list
        self.bet_type = bet_type
        self.is_virtual = is_virtual

    def batch_pay(self):
        now = tz.now_ts()
        coupon = get_coupon(self.coupon_id, self.user_id)
        if not coupon:
            raise DataError('coupon not exist or expired')
        # 此方法中有sql_wrapper
        if not check_coupon_valid(coupon.template_id, self.activity_type, self.bet_type):
            raise DataError('coupon invalid')

        # 严格验证coupon，并在事务中更改状态
        try:
            coupon = AccountCoupon.query.with_for_update().filter(
                AccountCoupon.id == self.coupon_id).filter(
                AccountCoupon.user_id == self.user_id).filter(
                AccountCoupon.start_ts < now).filter(
                AccountCoupon.expire_ts > now).filter(
                AccountCoupon.status == COUPON_STATUS.UNUSED).one()
        except Exception:
            raise DataError('no valid coupon')
        total_price = 0
        order_ids = []
        orders = []
        total_count = 0
        for info in self.buy_list:
            order = self.pay(info)
            total_price += order.price
            total_count += order.price / order.unit / order.times
            order_ids.append(order.id)
            orders.append(order)

        if (coupon.coupon_type == COUPON_TYPE.CONDITION_OFF and
                total_price < coupon.condition_price):
            raise DataError('coupon price not reached')

        real_pay = total_price - coupon.price
        real_pay = 0 if real_pay < 0 else real_pay

        account = decrease_balance_in_transaction(self.user_id, real_pay)
        coupon.status = COUPON_STATUS.USED
        coupon.save(auto_commit=False)

        create_sharding_transaction(self.user_id, {'type': TRANSACTION_TYPE.BALANCE_BUY,
                                                   'title': u'{}购彩'.format(
                                                       LOTTERY_TYPE.get_label(self.activity_type)),
                                                   'price': -real_pay,
                                                   'balance': account.balance,
                                                   'activity_type': self.activity_type,
                                                   'extend': json.dumps({
                                                       'order_ids': order_ids,
                                                       'order_price': total_price,
                                                       'coupon_id': coupon.id,
                                                       'coupon_price': coupon.price
                                                   }, cls=EnhencedEncoder),
                                                   'status': TRANSACTION_STATUS.DONE,
                                                   })

        orm.session.commit()
        for order in orders:
            tracker.track_pay(self.user_id, self.activity_type, order.bet_type, order.price, self.is_virtual)
        tracker.track_coupon(coupon.as_dict(), action='use')
        return total_count, real_pay

    def pay(self, info):
        try:
            activity = self.activity_table.query.filter(
                self.activity_table.term == info['term']).first()
            if activity is None:
                raise_with_traceback(err.TermExpired())
            if activity and (
                    activity.status != ACTIVITY_STATUS.STARTED or
                    tz.now_ts() > activity.end_ts):
                raise_with_traceback(err.TermExpired())
            info['track_status'] = TRACK_STATUS.NOP
            order = self.__create_order(info)
            orm.session.flush()
            self.__create_index(order)
            return order
        except (err.TermExpired, err.BalanceInsufficient) as e:
            raise_with_traceback(e)
        except Exception as e:
            _LOGGER.exception('account pay transaction operation error.(%s)' % e)
            orm.session.rollback()
            orm.session.close()
            raise_with_traceback(e)

    def __create_order(self, info):
        order = self.order_table()
        order.status = ORDER_STATUS.READY
        if 'track_id' in info:
            order.track_id = info['track_id']
        order.extend = json.dumps(info.get('extend', {}))
        for k in ('term', 'type', 'bet_type', 'number',
                  'price', 'times', 'unit', 'track_status'):
            setattr(order, k, info[k])
        order.user_id = self.user_id
        order.save(auto_commit=False)
        return order

    def __create_index(self, order):
        if order.track_id is None or \
                order.track_status == TRACK_STATUS.TRACKED:  # 当前期追号订单
            inst = OrderIndex()
            inst.order_id = order.id
            inst.user_id = order.user_id
            inst.activity_type = self.activity_type
            inst.save(auto_commit=False)


def check_buy_list_activity_type(buy_list):
    activity_type = buy_list[0]['activity_type']
    for each in buy_list:
        if each['activity_type'] != activity_type:
            raise DataError('activity_type not same!')
    return activity_type


def check_user_is_virtual(user_id):
    user = orm.session.query(Account).filter(Account.id == user_id).first()
    return user.is_virtual


DISABLE_BET_TYPES = {
    # LOTTERY_TYPE.BJ_PK10: [BJ_PK10_BET_TYPE.FIVE_FRONT, ],
    # LOTTERY_TYPE.FF_PK10: [FF_PK10_BET_TYPE.FIVE_FRONT, ],
    # LOTTERY_TYPE.JS_KS: [JS_KS_BET_TYPE.TWO_SAME_SINGLE, ],
    # LOTTERY_TYPE.GX_KS: [GX_KS_BET_TYPE.TWO_SAME_SINGLE, ],
    # LOTTERY_TYPE.FF_KS: [FF_KS_BET_TYPE.TWO_SAME_SINGLE, ],
    # LOTTERY_TYPE.CQ_SSC: [SSC_BET_TYPE.TWO_BACK_SUM, ],
    # LOTTERY_TYPE.TJ_SSC: [SSC_BET_TYPE.TWO_BACK_SUM, ],
    # LOTTERY_TYPE.XJ_SSC: [SSC_BET_TYPE.TWO_BACK_SUM, ],
    # # LOTTERY_TYPE.FF_SSC: [SSC_BET_TYPE.TWO_BACK_SUM, ],
    # LOTTERY_TYPE.CQ_SSC: [SSC_BET_TYPE.FUN_2, SSC_BET_TYPE.FUN_3, SSC_BET_TYPE.FUN_4],
    # LOTTERY_TYPE.TJ_SSC: [SSC_BET_TYPE.FUN_2, SSC_BET_TYPE.FUN_3, SSC_BET_TYPE.FUN_4],
    # LOTTERY_TYPE.XJ_SSC: [SSC_BET_TYPE.FUN_2, SSC_BET_TYPE.FUN_3, SSC_BET_TYPE.FUN_4],
    # LOTTERY_TYPE.FF_SSC: [SSC_BET_TYPE.FUN_2, SSC_BET_TYPE.FUN_3, SSC_BET_TYPE.FUN_4],

}


def check_buy_list_bet_type(buy_list):
    bet_type_list = []
    for i in range(len(buy_list)):
        bet_type = buy_list[i]['bet_type']
        bet_type_list.append(bet_type)

    for each in buy_list:
        # bet_type能不在bet_type_list？
        if each['bet_type'] not in bet_type_list:
            raise DataError('bet_type not same!')
        # patch for sick bet_type
        activity_type = each['activity_type']
        bet_type = each['bet_type']
        if activity_type in DISABLE_BET_TYPES and bet_type in DISABLE_BET_TYPES[activity_type]:
            raise DataError(u'该玩法暂时不可用')
    return bet_type_list
